<?php

namespace IMOcean\Utils;

use \Swoole\Coroutine;
use \Swoole\Coroutine\Scheduler;
use \Swoole\Coroutine\WaitGroup;

/**
 * Swoole coroutine coat.
 * 
 * class CoroutineCoat
 * @final
 * @package IMOcean\Utils
 */
final class CoroutineCoat
{
    /** Coroutine executor. */
    use CoroutineExecutor;

    public static function Run(callable $function, ...$args) : bool
    {
        $scheduler = new Scheduler();
        $options = Coroutine::getOptions();

        if (! isset($options['hook_flags'])) {
            $scheduler->set(['hook_flags' => SWOOLE_HOOK_ALL]);
        }
        $scheduler->add($function, ...$args);

        return $scheduler->start();
    }

    public static function Go(callable $function, ...$args) : mixed
    {
        return Coroutine::create($function, ...$args);
    }

    public static function Defer(callable $function) : void
    {
        Coroutine::defer($function);
    }

    public static function Batch(array $tasks, float $timeout = -1) : array
    {
        $waitGroup = new WaitGroup(count($tasks));

        foreach ($tasks as $id => $task) {
            Coroutine::create(function () use ($waitGroup, &$tasks, $id, $task) {
                $tasks[$id] = null;
                $tasks[$id] = $task();
                $waitGroup->done();
            });
        }
        $waitGroup->wait($timeout);

        return $tasks;
    }

    public static function Parallel(int $number, callable $function) : void
    {
        $count = $number;
        $waitGroup = new WaitGroup($number);

        while ($count--) {
            Coroutine::create(function () use ($function, $waitGroup) {
                $function();
                $waitGroup->done();
            });
        }
        $waitGroup->wait();
    }

    public static function Map(array $list, callable $function, float $timeout = -1) : array
    {
        $waitGroup = new WaitGroup(count($list));
        foreach ($list as $id => $element) {
            Coroutine::create(function () use ($waitGroup, &$list, $id, $element, $function): void {
                $list[$id] = null;
                $list[$id] = $function($element);
                $waitGroup->done();
            });
        }
        $waitGroup->wait($timeout);

        return $list;
    }
}